Debugging with GDB

GDB is a multi-language debugger that already supports C, C++, Modula-2 and Fortran, and several changes have been made to support Lisp debugging. Some of these changes are unusual because they rely on the underlying Lisp process to actively cooperate with GDB, which is quite different from the normally passive role the inferior process plays.

Figure: WCL Running the VMACS Electronic Design Notebook
\begin{figure*}% To produce the screen dump:
\epsfbox{solenoid.ps}
\end{figure*}

A Lisp name demangler has been added to the parts of GDB that print frame names. Thus, backtraces now show mangled Lisp names as they originally appeared, while still displaying regular C function names correctly. The frame handling code in GDB also has special knowledge about the names of functions in the Lisp evaluator so that frames that are really being used to interpret Lisp code are either hidden or are displayed as the name of an interpreted function. When examining an interpreted stack frame, it is also possible to recursively enter the Lisp evaluator with the current lexical environment using a new GDB command called eval.

The eval command uses GDB's ability to call functions in the process being debugged. When eval is called, the name of the current frame is examined. If GDB recognizes that the frame is part of the evaluator, then a new read-eval-print loop is started in the underlying process using the current lexical environment. A small portion of the evaluator is written in C to allow convenient access to this environment. This loop allows access by name to local variables, and thus any Lisp expression may be interactively evaluated in the lexical environment of an interpreted function. If the current frame is not an interpreted function, but is instead a C function or a compiled Lisp function, then the read-eval-print loop is run in the null lexical environment.

GDB is also integrated with the Common Lisp condition system by using GDB's ability to call functions in the inferior Lisp process. The new command info restarts shows all available restart options, while the command restart allows the user to select an option. The new abort command is also available as a convenient short cut for aborting to top-level by selecting the outermost abort restart. This command is especially useful when Lisp is in an infinite loop and control-c is used to interrupt the computation. The standard GDB continue command can also be used to continue an interrupted computation, or to select a restart option.


Table: ``Hello World!'' Statistics
Metric WCL-2.0 Lucid 4.0 AKCL 1-530
executable size 40k 1564k 2440k
text size 32k 1040k 1290k
data size 8k 475k 1150k
minimum start+exit time 1.2s .3s .01s


Source level debugging of compiled Lisp functions is not yet supported. Instead, the translated C code must be debugged. Although source level debugging of Lisp using GDB is the ultimate goal of WCL, debugging the C code is still feasible. Variables in compiled Lisp functions can be examined with the new GDB lprint command. Lprint accepts the mangled name of a Lisp variable, and calls the printer in the underlying Lisp process with the value of that variable.

The GDB add-symbol-file command already supports adding the symbol table and debugging information of a file that has been dynamically loaded at a known address in the inferior process. However, debugging dynamically loaded Lisp code also required a few changes to GDB. After Lisp loads a compiled file, a temporary file is written containing the name of the loaded file and the address at which it was loaded. WCL then uses the Unix kill system call to send SIGUSR1, a user defined Unix signal, to itself. Because GDB is debugging WCL, it intercepts this signal and understands that it should update its symbol table using the information in the temporary file. Execution of WCL is then resumed normally, and the dynamically loaded file can be fully debugged without any intervention from the user.